/***************************************************************************
Name:		client.cpp
Autor:		Kevin Huber
Version:	0.4 Alpha
Last Edit:      12.06.2009

Beschreibung:
Diese Klasse stellt einen Clienten dar mit dem interagiert werden kann.
***************************************************************************/

#include "client.h"
#include "clientmanager.h"
#include <string>
#include "chathandler.h"
#include "sqlconnection.h"
#include <QHostAddress>
#include <time.h>

Client::Client(ClientManager *clientManager, unsigned int cId, QTcpSocket *socket, SQLConnection *nSQLConnection, Client_Status cStatus, QObject *parent) : QObject(parent)
{
    m_id            = cId;
    m_socket        = new ClientSocket(socket, this);
    m_firstUpdate   = true;

    isNotPlaying();
    m_nextClient = m_lastClient = NULL;

    m_loginName = "Unknown";
    m_status    = cStatus;

    m_clientManager = clientManager;
    m_TunnelDatabase = nSQLConnection;

    m_chatHandler = new ChatHandler(this);

    // Events verknuepfen
    connect( m_socket->getTcpSocket(), SIGNAL(disconnected()), this, SLOT(on_disconnected()) );
    connect( m_socket, SIGNAL(newPackage(Package*)), m_chatHandler, SLOT(handlePackage(Package*)) );
    connect( m_chatHandler, SIGNAL(authCommand(QString)), this, SLOT(on_authCommand(QString)) );
}

Client::~Client()
{
    if( getLastClient() )
        getLastClient()->setNextClient(getNextClient());

    if( getNextClient() )
        getNextClient()->setLastClient(getLastClient());

    delete m_chatHandler;
}

void Client::setName(const QString &newName)
{
    m_nickname = newName;
}

void Client::setPlayingGame(const QString &gName)
{
    m_playing  = true;
    m_gameName = gName;
}

void Client::on_disconnected()
{
    m_status = disconnected;
    emit signal_goOffline(m_loginName);
    emit signal_disconnected(this);

    m_socket->deleteLater();
}

void Client::on_authCommand(const QString &arguments)
{
    if( arguments.length() > 9 && arguments.toStdString().substr(0,9) == "AcceptMe:" )
    {
        // Arguments have the following syntax:'AcceptMe:loginName|password'
        // Check if loginName and password are matching to an account in our database
        QString passwordHash = arguments.mid( arguments.indexOf("|") + 1 );
        QString username     = arguments.mid( 9, arguments.toStdString().find("|") - 9 );

        if( m_TunnelDatabase->checkLogin(username, passwordHash) )
        {
            time_t t          = time(NULL);
            quint32 uid       = m_TunnelDatabase->getClientUid(username);
            QString timestamp = QString("%1").arg(t);

            if( m_clientManager->getClient(uid) )
            {
                Client *client = m_clientManager->getClient(uid);
                Package package;

                setupSystemPackage(&package, TUNNEL_COMMANDCATEGORY_ERROR, TUNNEL_COMMAND_ERRORMESSAGE, "This account is used by antoher computer!\nYou got disconnected form the server.");
                client->getSocket()->sendPackage(&package);

                // For save delete we have to do this.
                m_clientManager->removeClient(client, "This account is used by antoher computer!");
            }

            // Changing the clients state to connected, so that he can gain full access to all functions
            Package package;

            setupSystemPackage(&package, TUNNEL_COMMANDCATEGORY_COMM, TUNNEL_COMMAND_AUTH, "Welcome");
            m_socket->sendPackage(&package);

            // Update ClientData
            m_status    = connected;
            m_id        = uid;
            m_loginName = username;
            printf("Changed %s's id to %d\n", username.toStdString().c_str(), m_id);

            // Update lastip and timestamp
            m_TunnelDatabase->updateLastip(m_id, m_socket->getTcpSocket()->peerAddress().toString());
            m_TunnelDatabase->updateLastseen(m_id, timestamp);

            // Connect Clients events
            connect( m_chatHandler, SIGNAL(newGlobalMessage(QString)), this, SLOT(emitNewGlobalMessage(QString)) );
            connect( m_chatHandler, SIGNAL(newPrivateMessage(QString, QString)), this, SLOT(emitNewPrivateMessage(QString, QString)) );
            connect( m_chatHandler, SIGNAL(getFriendlist()), this, SLOT(on_getFriendlist()));
            connect( m_chatHandler, SIGNAL(addFriend(QString)), this, SLOT(on_addFriend(QString)));
            connect( m_chatHandler, SIGNAL(getFriendrequests()), this, SLOT(on_getFriendrequests()) );
            connect( m_chatHandler, SIGNAL(acceptFriendInventation(quint32)), this, SLOT(on_acceptFriendInventation(quint32)));

            return;
        }
        else
        {
            // Counting violations is an good idea here?
            Package package;

            setupSystemPackage(&package, TUNNEL_COMMANDCATEGORY_COMM, TUNNEL_COMMAND_AUTH, "Refused:Wrong loginname and/or password!");
            m_socket->sendPackage(&package);

            // For save delete we have to do this :S
            m_clientManager->removeClient(this, "Wrong loginname and/or password!");
            return;
        }
    }
    else
        printf("Wrong Arguments for Auth: \"%s\"\n", arguments.toStdString().c_str());
}

void Client::on_getFriendlist()
{
    QString sFriendlist = ""; // String in wich we store the friendlist.

    if( m_status != connected )
        return;

    Package package;

    // Get Clients friendlist
    std::vector<ClientFriend> myFriends(0);
    m_TunnelDatabase->getFriendlist(&myFriends, m_id);

    // Convert all ClientFriend objects into an string and append it to the arguments part of the package...
    foreach( ClientFriend myFriend, myFriends )
    {
        // name:nickName:online;name2:nickname2:offline;...
        sFriendlist += myFriend.loginName + ":";
        sFriendlist += (myFriend.nickName.length() > 0 ? myFriend.nickName : myFriend.loginName) + ":"; // If the client has no nickname take the loginname.
        sFriendlist += (myFriend.isOnline ? "online" : "offline");
        sFriendlist += ";";

        if( myFriend.isOnline )
        {
            Client *myFriend_client = m_clientManager->getClient(myFriend.uid);

            // Connecting events between those two clients but first disconnect all Signals and slots between those two objects.
            disconnect(myFriend_client, 0, this, 0);
            disconnect(this, 0, myFriend_client, 0);
            connect( myFriend_client, SIGNAL(signal_goOffline(QString)), this, SLOT(on_friendGoesOffline(QString)) );
            connect( this, SIGNAL(signal_goOffline(QString)), myFriend_client, SLOT(on_friendGoesOffline(QString)) );

            // Send online notification to friend.
            if( m_firstUpdate )
                myFriend_client->sendNotification_friendComesOnline(m_loginName);
        }
    }

    setupSystemPackage(&package, TUNNEL_COMMANDCATEGORY_FRIENDS, TUNNEL_COMMAND_UPDATEFRIENDLIST, sFriendlist);
    m_socket->sendPackage(&package);

    m_firstUpdate = false;
}

void Client::on_addFriend(const QString &arguments)
{
    if( m_status != connected )
        return;
    if( arguments.length() < 1 )
        return;

    QString friend_loginName = arguments.left( arguments.indexOf(':') );
    QString rMessage         = arguments.mid(arguments.toStdString().find(':') + 1);
    quint32 friend_uid       = m_TunnelDatabase->getClientUid(friend_loginName);

    if( friend_uid < 1 )
        return;

    // Should make some status messages for the client here!
    if( friend_loginName > 0 )
        m_TunnelDatabase->addFriend(m_id, friend_loginName, rMessage);

    // If the client is currently loggedin, we send him the friendrequest.
    Client *myFriend = m_clientManager->getClient(friend_uid);
    if( myFriend )
    {
        myFriend->on_getFriendrequests();
    }
}

void Client::emitNewGlobalMessage(const QString &message)
{
    if( m_status == connected )
        emit signal_newGlobalMessage(getName(), message);
}

void Client::emitNewPrivateMessage(const QString &message, const QString &to)
{
    if( m_status == connected )
        emit signal_newPrivateMessage(getName(), message, to);
}

void Client::on_getFriendrequests()
{
    QString sFriendrequests = ""; // String in wich we store the friendrequests.

    if( m_status != connected )
        return;

    Package package;

    // Get friendrequests
    std::vector<ClientFriendrequest> friendrequests(0);
    m_TunnelDatabase->getFriendrequests(&friendrequests, m_id);

    // Convert all ClientFriendrequest objects into an string and append it to the arguments part of the package...
    foreach( ClientFriendrequest friendrequest, friendrequests )
    {
        // uid:nickName:requestMessage;uid2:nickname2:requestMessage;...
        sFriendrequests = sFriendrequests.sprintf("%d:", friendrequest.applicant_uid);
        sFriendrequests += (friendrequest.applicant_nickName.length() > 0 ? friendrequest.applicant_nickName : friendrequest.applicant_loginName) + ":"; // If the client has no nickname take the loginname.
        sFriendrequests += friendrequest.rMessage;
        sFriendrequests += ";";
    }

    setupSystemPackage(&package, TUNNEL_COMMANDCATEGORY_FRIENDS, TUNNEL_COMMAND_GETFRIENDREQUESTS, sFriendrequests);
    m_socket->sendPackage(&package);
}

void Client::on_acceptFriendInventation(const quint32 &applicant_uid)
{
    if( m_status != connected )
        return;

    m_TunnelDatabase->acceptFriendrequest(m_id, applicant_uid);

    // If the client is currently loggedin, we send him his friendlist again.
    Client *myFriend = m_clientManager->getClient(applicant_uid);
    if( myFriend )
    {
        myFriend->on_getFriendlist();

        // Connecting events between those two clients but first disconnect all Signals and slots between those two objects.
        disconnect(myFriend, 0, this, 0);
        disconnect(this, 0, myFriend, 0);
        connect( myFriend, SIGNAL(signal_goOffline(QString)), this, SLOT(on_friendGoesOffline(QString)) );
        connect( this, SIGNAL(signal_goOffline(QString)), myFriend, SLOT(on_friendGoesOffline(QString)) );

        // Send online notification to friend.
        myFriend->sendNotification_friendComesOnline(m_loginName);
        sendNotification_friendComesOnline(myFriend->getName());
    }
}

void Client::sendNotification_friendComesOnline(const QString &friend_loginName)
{
    Package package;

    setupSystemPackage(&package, TUNNEL_COMMANDCATEGORY_FRIENDS, TUNNEL_COMMAND_FRIENDGOESONLINE, friend_loginName);
    m_socket->sendPackage(&package);
}

void Client::on_friendGoesOffline(const QString &friend_loginName)
{
    Package package;

    setupSystemPackage(&package, TUNNEL_COMMANDCATEGORY_FRIENDS, TUNNEL_COMMAND_FRIENDGOESOFFLINE, friend_loginName);
    m_socket->sendPackage(&package);
}
